package com.yx.elastic.elasticcm.orm;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.yx.elastic.elasticcm.util.StringUtils;
import org.elasticsearch.action.get.GetResponse;

import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.math.BigDecimal;
import java.util.*;

import static com.yx.elastic.elasticcm.orm.EFieldStrategy.IGNORED;
import static com.yx.elastic.elasticcm.orm.ElasORMExceptionCode.*;

/**
 * Created by chenzp on 18-8-2.
 */
public class ElasORM {

    private static  Map<Class, ElasORMClass> cacheROMclasses = new HashMap<>();

    /**
     * 序列化对象的转elstic orm对象
     * @param object
     * @return
     * @throws NoSuchMethodException
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     */
    public static ElasObject conversion(Object object) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {

        ElasORMClass eclass = cacheROMclasses.get(object.getClass());

        if (eclass == null) {
            eclass = parse(object);
            cacheROMclasses.put(object.getClass(), eclass);
        }

        ElasObject elasObject = new ElasObject();
        elasObject.setIndex(eclass.getIndex());
        elasObject.setType(eclass.getType());

        //设置文档id值
        if (eclass.getIdType().equals(EIdType.INPUT) || eclass.getIdType().equals(EIdType.AUTO)) {
            String methodField = eclass.getOrmIdName().substring(0, 1).toUpperCase() + eclass.getOrmIdName().substring(1);
            Method getMethod = eclass.getIdClass().getDeclaredMethod("get" + methodField);
            Object invoke = getMethod.invoke(object);

            if (eclass.getIdType().equals(EIdType.AUTO)) {
                if (invoke != null) {
                    elasObject.setId(invoke.toString());
                }
            }else{
                try{
                    elasObject.setId(invoke.toString());
                }catch (RuntimeException e){
                    throw new ElasORMException(NOT_ID);
                }
            }

        }

        JSONObject data = new JSONObject();

        for (ElasORMClass.FieldAttr fieldAttr : eclass.getAttrs()) {
            fieldAttr.getFieldName();

            if (!fieldAttr.isExit()) {
                    continue;
            }

            String methodField = fieldAttr.getOrmFieldName().substring(0, 1).toUpperCase() + fieldAttr.getOrmFieldName().substring(1);
            Method getMethod = fieldAttr.getAttrClass().getDeclaredMethod("get" + methodField);
            Object invoke = getMethod.invoke(object);

            if (fieldAttr.getStrategy().equals(EFieldStrategy.NOT_NULL) && invoke == null) {
                continue;
            }

            if (fieldAttr.getStrategy().equals(EFieldStrategy.NOT_EMPTY) && invoke!= null && StringUtils.isEmpty(invoke.toString())) {
                continue;
            }

            if (invoke == null) {
                data.put(fieldAttr.getFieldName(), null);
            }else{
                Object elasfieldVal = getElasfieldVal(invoke);
                data.put(fieldAttr.getFieldName(), elasfieldVal);
            }
        }

        elasObject.setJson(data);
        return  elasObject;
    }

    /**
     * 解析多想
     *
     * @param object
     * @return
     */
    private static ElasORMClass parse(Object object) {

        ElasORMClass eclass = new ElasORMClass();

        List<ElasORMClass.FieldAttr> attrs = new ArrayList<>();

        eclass.setEclass(object.getClass());

        boolean isParentClass = false;

        if (!object.getClass().isAnnotationPresent(EDoc.class)) {

            if (object.getClass().getSuperclass().isAnnotationPresent(EDoc.class)) {
                isParentClass  = true;
            }else{
                throw new ElasORMException(object.getClass().toString(),NOT_DOC);
            }
        }

        EDoc eDoc = null;
        if (isParentClass) {
             eDoc = object.getClass().getSuperclass().getDeclaredAnnotation(EDoc.class);
        }else{
             eDoc = object.getClass().getDeclaredAnnotation(EDoc.class);
        }

        if (StringUtils.isNullAndEmpty(eDoc.index())) {
            throw new ElasORMException(object.getClass().toString(), NOT_DOC_INDEX);
        }

        if (StringUtils.isNullAndEmpty(eDoc.type())) {
            throw new ElasORMException(object.getClass().toString(), NOT_DOC_TYPE);
        }

        //设置索引
        eclass.setIndex(eDoc.index());
        //设置文档类型
        eclass.setType(eDoc.type());

        List<Map<String,Object>>  fields = new ArrayList<>();

        for (Field field : object.getClass().getDeclaredFields()) {
            Map<String,Object> fMap = new HashMap();
            fMap.put("class", object.getClass());
            fMap.put("field", field);
            fields.add(fMap);
        }

        //判断注解是否存在父类，存在合并属性
        if (isParentClass) {
            for (Field field : object.getClass().getSuperclass().getDeclaredFields()) {
                Map<String,Object> fMap = new HashMap();
                fMap.put("class", object.getClass().getSuperclass());
                fMap.put("field", field);
                fields.add(fMap);
            }
        }

        //是否存在主键ID
        boolean existEid = false;

        //设置eclass属性map集合
        Map<String, ElasORMClass.FieldAttr> fieldAttrs = new HashMap<>();

        //遍历属性
        for (Map<String,Object> map : fields) {

            Field field = (Field) map.get("field");

            Class oClass = (Class) map.get("class");

            boolean[] validate = new boolean[]{
                    field.isAnnotationPresent(EDocId.class)
                    , field.isAnnotationPresent(EField.class)
                    , field.isAnnotationPresent(ENon.class)
            };

            //属性是否存在set get 方法
            boolean existGetSetMethod = true;

            String methodField = field.getName().substring(0, 1).toUpperCase() + field.getName().substring(1);
            try {
                oClass.getDeclaredMethod("set" + methodField, field.getType());
                oClass.getDeclaredMethod("get" + methodField);

            } catch (NoSuchMethodException e) {
                existGetSetMethod = false;
            }

            if (validate[0]) {//id注解处理

                if (existEid) {
                    throw new ElasORMException(oClass + " ID ", MORE_ANNOTATION);
                }

                if (!existGetSetMethod) {
                    throw new ElasORMException(oClass + "属性" + field.getName(), NOT_METHOD);
                }

                if (validate[1] || validate[2]) {
                    throw new ElasORMException(MORE_ANNOTATION);
                }
                EDocId eDocId = field.getDeclaredAnnotation(EDocId.class);
                if (StringUtils.isEmpty(eDocId.fieldName())) {
                    eclass.setIdName(field.getName());
                } else {
                    eclass.setIdName(eDocId.fieldName());
                }
                eclass.setIdType(eDocId.type());
                eclass.setOrmIdName(field.getName());
                eclass.setIdClass(oClass);
                existEid = true;

            } else if (validate[1]) {//其他属性注解处理

                if (!existGetSetMethod) {
                    throw new ElasORMException(object.getClass() + "属性" + field.getName(), NOT_METHOD);
                }

                if (validate[2]) {
                    throw new ElasORMException(object.getClass() + "属性" + field.getName(), MORE_ANNOTATION);
                }

                EField eField = field.getDeclaredAnnotation(EField.class);

                ElasORMClass.FieldAttr fieldAttr = new ElasORMClass.FieldAttr();
                if (StringUtils.isEmpty(eField.value())) {
                    fieldAttr.setFieldName(field.getName());
                } else {
                    fieldAttr.setFieldName(eField.value());
                }

                fieldAttr.setExit(eField.exist());

                fieldAttr.setStrategy(eField.validate());
                fieldAttr.setOrmFieldName(field.getName());
                fieldAttr.setAttrClass(oClass);
                fieldAttrs.put(field.getName(),fieldAttr);

            } else {//只有ENon注解和或者没有其他注解

                if (!existGetSetMethod) {
                    continue;
                }

                ElasORMClass.FieldAttr fieldAttr = new ElasORMClass.FieldAttr();

                fieldAttr.setFieldName(field.getName());

                if (validate[2]) {
                    ENon eNon = field.getDeclaredAnnotation(ENon.class);
                    fieldAttr.setExit(!eNon.value());
                }else{
                    fieldAttr.setExit(true);
                }

                fieldAttr.setExit(!validate[2]);
                fieldAttr.setStrategy(IGNORED);
                fieldAttr.setOrmFieldName(field.getName());
                fieldAttr.setAttrClass(oClass);
                fieldAttrs.put(field.getName(),fieldAttr);
            }
        }

        if (!existEid) {
            throw new ElasORMException(NOT_ID);
        }

        for (Map.Entry entry : fieldAttrs.entrySet()) {
            attrs.add((ElasORMClass.FieldAttr) entry.getValue());
        }

        eclass.setAttrs(attrs);
        return eclass;
    }

    /**
     * 获取EDoc注解信息
     *
     * @param entity
     * @return
     */
    private EDoc getEDoc(Object entity) {

        if (entity.getClass().isAnnotationPresent(EDoc.class)) {
            for (Annotation anno : entity.getClass().getDeclaredAnnotations()) {//获得所有的注解
                if (anno.annotationType().equals(EDoc.class)) {//找到自己的注解
                    EDoc eDocxAnno = (EDoc) anno;

                    if (StringUtils.isNullAndEmpty(eDocxAnno.index())) {
                        throw new ElasORMException(entity.getClass().toString(), NOT_DOC_INDEX);
                    }

                    if (StringUtils.isNullAndEmpty(eDocxAnno.type())) {
                        throw new ElasORMException(entity.getClass().toString(), NOT_DOC_TYPE);
                    }
                    return eDocxAnno;
                }
            }
        }

        return null;
    }

    /**
     * 获取属性值转为elastic值
     * @param object
     * @return
     */
    private static Object getElasfieldVal(Object object) {
        if (object.getClass() == String.class
                ||object.getClass() == Integer.class
                ||object.getClass() == Long.class
                ||object.getClass() == Float.class
                ||object.getClass() == Short.class
                ||object.getClass() == Byte.class
                ||object.getClass() == Double.class
                ||object.getClass() == Character.class
                ||object.getClass() == Boolean.class
                ) {
            return object;
        }

        if (object.getClass() == Date.class) {
            Date date = (Date) object;
            return ((Date)object).getTime();
        }

        if (object.getClass() == java.sql.Date.class) {
            return ((java.sql.Date) object).getTime();
        }

        if (object.getClass() == Calendar.class) {
            return ((Calendar) object).getTime();
        }

        if (object.getClass() == BigDecimal.class) {
            // TODO: 18-8-3 类型不确定这块后期需要考虑
            return ((BigDecimal) object).doubleValue();
        }

        // TODO: 18-8-3 这块后期复杂的类型，或者不能转json类型需要考虑
        return  JSON.toJSON(object);
    }

    /**
     * json对象转elatic orm对象
     * @param getResponse
     * @param object
     * @return
     */
    public static Object jsonToOrmObject(GetResponse getResponse, Object object) {
        ElasORMClass eclass = cacheROMclasses.get(object.getClass());

        if (eclass == null) {
            eclass = parse(object);
            cacheROMclasses.put(object.getClass(), eclass);
        }
        JSONObject newJSON = new JSONObject();

        newJSON.put(eclass.getOrmIdName(), getResponse.getId());

        for (ElasORMClass.FieldAttr fieldAttr : eclass.getAttrs()) {
            if (getResponse.getSource().get(fieldAttr.getFieldName()) != null) {
                newJSON.put(fieldAttr.getOrmFieldName(),getResponse.getSource().get(fieldAttr.getFieldName()));
            }
        }
        return JSON.toJavaObject(newJSON,object.getClass());
    }


    public static ElasORMClass typeToOrmClass(Type type) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        Object o = typeToObj(type);
        return parse(o);
    }

    /**
     * 反射type转实例化对象
     * @param type
     * @return
     * @throws ClassNotFoundException
     * @throws IllegalAccessException
     * @throws InstantiationException
     */
    public static Object typeToObj(Type type) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        Class clazz = null;
        ParameterizedType parameterizedType = null;
        if (type instanceof ParameterizedType) {
            parameterizedType = (ParameterizedType) type;
            Type[] typeArray = parameterizedType.getActualTypeArguments();
            if (typeArray != null && typeArray.length > 0) {
                clazz=(Class)typeArray[0];
            }
        }
        Object aClass = Class.forName(clazz.getName()).newInstance();
        return aClass;
    }

}
